package com.dbflow5.processor.definition;

import com.dbflow5.annotation.Column;
import com.dbflow5.annotation.ColumnMap;
import com.dbflow5.annotation.QueryModel;
import com.dbflow5.processor.ClassNames;
import com.dbflow5.processor.MethodDefinition;
import com.dbflow5.processor.ProcessorManager;
import com.dbflow5.processor.Validators;
import com.dbflow5.processor.definition.behavior.Behaviors;
import com.dbflow5.processor.definition.column.ColumnDefinition;
import com.dbflow5.processor.utils.ElementUtility;
import com.dbflow5.processor.utils.ProcessorUtils;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;

import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import java.util.List;

/**
 * Description:
 */
public class QueryModelDefinition extends EntityDefinition {
    private final Behaviors.AssociationalBehavior associationalBehavior;
    private final Behaviors.CursorHandlingBehavior cursorHandlingBehavior;

    public QueryModelDefinition(Behaviors.AssociationalBehavior associationalBehavior, Behaviors.CursorHandlingBehavior cursorHandlingBehavior,
                                TypeElement typeElement, ProcessorManager processorManager) {
        super(typeElement, processorManager);
        this.associationalBehavior = associationalBehavior;
        this.cursorHandlingBehavior = cursorHandlingBehavior;

        setOutputClassName("_QueryTable");
        processorManager.addModelToDatabase(elementClassName, associationalBehavior.databaseTypeName);
    }

    @Override
    public Behaviors.AssociationalBehavior associationalBehavior() {
        return associationalBehavior;
    }

    @Override
    public Behaviors.CursorHandlingBehavior cursorHandlingBehavior() {
        return cursorHandlingBehavior;
    }

    @Override
    public MethodDefinition[] methods() {
        return new MethodDefinition[]{
                new Methods.LoadFromCursorMethod(this),
                new Methods.ExistenceMethod(this),
                new Methods.PrimaryConditionMethod(this)
        };
    }

    public QueryModelDefinition(QueryModel queryModel, TypeElement typeElement, ProcessorManager processorManager) {
        this(
        new Behaviors.AssociationalBehavior(typeElement.getSimpleName().toString(), ProcessorUtils.extractTypeNameFromAnnotation(queryModel, e -> null, queryModel1 -> {
            queryModel.database();
            return null;
        }), queryModel.allFields()),
        new Behaviors.CursorHandlingBehavior(queryModel.orderedCursorLookUp(), queryModel.assignDefaultValuesFromCursor()),
        typeElement, processorManager);
    }

    /**
     * [ColumnMap] constructor.
     */
    public QueryModelDefinition(TypeElement typeElement, TypeName databaseTypeName, ProcessorManager processorManager) {
        this(new Behaviors.AssociationalBehavior(typeElement.getSimpleName().toString(),databaseTypeName,true),
             new Behaviors.CursorHandlingBehavior(false, true),
             typeElement, processorManager);
    }

    public void prepareForWriteInternal() {
        if(typeElement != null) {
            createColumnDefinitions(typeElement);
        }
    }

    @Override
    public TypeName extendsClass() {
        return ParameterizedTypeName.get(ClassNames.RETRIEVAL_ADAPTER, elementClassName);
    }

    @Override
    public void onWriteDefinition(TypeSpec.Builder typeBuilder) {
        if(elementClassName != null) {
            columnDefinitions.forEach(it -> {
                it.addPropertyDefinition(typeBuilder, elementClassName);
            });
        }

        writeGetModelClass(typeBuilder, elementClassName);
        this.writeConstructor(typeBuilder);

        MethodDefinition[] methodArray = methods();
        for(MethodDefinition definition : methodArray) {
            if(definition != null){
                typeBuilder.addMethod(definition.methodSpec());
            }
        }
    }

    @Override
    public void createColumnDefinitions(TypeElement typeElement) {
        BasicColumnGenerator columnGenerator = new BasicColumnGenerator(manager);
        List<Element> variableElements = ElementUtility.getAllElements(typeElement, manager);
        for (Element element : variableElements) {
            classElementLookUpMap.put(element.getSimpleName().toString(), element);
        }

        Validators.ColumnValidator columnValidator = new Validators.ColumnValidator();
        for (Element variableElement : variableElements) {

            // no private static or final fields
            boolean isAllFields = ElementUtility.isValidAllFields(associationalBehavior.allFields, variableElement);
            // package private, will generate helper
            boolean isColumnMap = variableElement.getAnnotation(ColumnMap.class) != null;

            if (variableElement.getAnnotation(Column.class) != null || isAllFields || isColumnMap) {
                boolean isPackagePrivate = ElementUtility.isPackagePrivate(element);
                ColumnDefinition columnDefinition = columnGenerator.generate(variableElement, this);
                if(columnDefinition != null) {
                    if (columnValidator.validate(manager, columnDefinition)) {
                        columnDefinitions.add(columnDefinition);
                        if (isPackagePrivate) {
                            packagePrivateList.add(columnDefinition);
                        }
                    }

                    if (columnDefinition.type.isPrimaryField()) {
                        manager.logError("QueryModel $elementName cannot have primary keys");
                    }
                }
            }
        }
    }

    @Override
    public List<ColumnDefinition> primaryColumnDefinitions() {
        return columnDefinitions;
    }
}
